package com.abso.sunlight.explorer.wizards;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jxl.Workbook;
import jxl.write.Boolean;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;

import org.apache.commons.lang.mutable.MutableInt;
import org.eclipse.core.runtime.IProgressMonitor;

import com.abso.sunlight.api.Legislator;
import com.abso.sunlight.api.SunlightException;

/**
 * The Excel Exporter is able to export a list of legislators into an Excel spreadsheet.
 */
public class ExcelExporter {

    /** The list of legislators to export. */
    private List<Legislator> legislators;

    /** The destination file. */
    private File file;

    /** The set of columns' lengths, addressed by column indices. */
    private Map<Integer, MutableInt> colTextLengths;

    /** The Excel sheet built by the Excel Exporter. */
    private WritableSheet sheet;

    /**
     * Constructs a new Excel Exporter.
     * 
     * @param legislators
     *            the list of legislators to export.
     * @param file
     *            the destination file.
     */
    public ExcelExporter(List<Legislator> legislators, File file) {
        this.legislators = legislators;
        this.file = file;
        colTextLengths = new HashMap<Integer, MutableInt>();
    }

    /**
     * Performs the export.
     * 
     * @param monitor
     *            the progress monitor.
     * @throws SunlightException
     *             if an exception occurred performing the export operation.
     */
    public void export(IProgressMonitor monitor) throws SunlightException {
        WritableWorkbook workbook = null;
        try {
            monitor.beginTask("Exporting", legislators.size() + 6);
            workbook = Workbook.createWorkbook(file);
            monitor.worked(1);
            sheet = workbook.createSheet("Legislators", 0);
            monitor.worked(1);
            writeHeader();
            monitor.worked(1);
            for (int row = 1; row <= legislators.size(); row++) {
                writeRow(row);
                if (monitor.isCanceled()) {
                    return;
                }
                monitor.worked(1);
            }
            adjustColumnsSize();
            monitor.worked(1);
            workbook.write();
            monitor.worked(1);
        } catch (IOException e) {
            throw new SunlightException(e);
        } catch (WriteException e) {
            throw new SunlightException(e);
        } finally {
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (WriteException e) {
                } catch (IOException e) {
                }
            }
            monitor.worked(1);
            monitor.done();
        }
    }

    /**
     * Writes the spreadsheet's header.
     * 
     * @throws RowsExceededException
     *             if
     * @throws WriteException
     */
    private void writeHeader() throws RowsExceededException, WriteException {
        writeStringCell(0, 0, sheet, "Title");
        writeStringCell(1, 0, sheet, "First Name");
        writeStringCell(2, 0, sheet, "Middle Name");
        writeStringCell(3, 0, sheet, "Last Name");
        writeStringCell(4, 0, sheet, "Name Suffix");
        writeStringCell(5, 0, sheet, "Nickname");
        writeStringCell(6, 0, sheet, "Party");
        writeStringCell(7, 0, sheet, "State");
        writeStringCell(8, 0, sheet, "District");
        writeStringCell(9, 0, sheet, "In Office");
        writeStringCell(10, 0, sheet, "Gender");
        writeStringCell(11, 0, sheet, "Phone");
        writeStringCell(12, 0, sheet, "Fax");
        writeStringCell(13, 0, sheet, "Web Site");
        writeStringCell(14, 0, sheet, "Web Contact Form");
        writeStringCell(15, 0, sheet, "Email");
        writeStringCell(16, 0, sheet, "Congress Office");
        writeStringCell(17, 0, sheet, "Bioguide ID");
        writeStringCell(18, 0, sheet, "Vote Smart ID");
        writeStringCell(19, 0, sheet, "FEC ID");
        writeStringCell(20, 0, sheet, "GovTrack.us ID");
        writeStringCell(21, 0, sheet, "CRP ID");
        writeStringCell(22, 0, sheet, "Evenful.com ID");
        writeStringCell(23, 0, sheet, "Congresspedia URL");
        writeStringCell(24, 0, sheet, "Twitter ID");
        writeStringCell(25, 0, sheet, "YouTube ID");
    }

    /**
     * Writes a spreadsheet's row.
     * 
     * @param row
     *            the row number.
     * @throws WriteException
     *             if an exception occurred writing the row.
     */
    private void writeRow(int row) throws WriteException {
        Legislator legislator = legislators.get(row - 1);
        writeStringCell(0, row, sheet, legislator.getTitle().toString());
        writeStringCell(1, row, sheet, legislator.getFirstName());
        writeStringCell(2, row, sheet, legislator.getMiddleName());
        writeStringCell(3, row, sheet, legislator.getLastName());
        writeStringCell(4, row, sheet, legislator.getNameSuffix());
        writeStringCell(5, row, sheet, legislator.getNickname());
        writeStringCell(6, row, sheet, legislator.getParty().toString());
        writeStringCell(7, row, sheet, legislator.getState().toString());
        writeStringCell(8, row, sheet, legislator.getDistrict());
        writeBooleanCell(9, row, sheet, legislator.isInOffice());
        writeStringCell(10, row, sheet, legislator.getGender().toString());
        writeStringCell(11, row, sheet, legislator.getPhone());
        writeStringCell(12, row, sheet, legislator.getFax());
        writeStringCell(13, row, sheet, legislator.getWebSite());
        writeStringCell(14, row, sheet, legislator.getWebContactForm());
        writeStringCell(15, row, sheet, legislator.getEmail());
        writeStringCell(16, row, sheet, legislator.getCongressOffice());
        writeStringCell(17, row, sheet, legislator.getBioguideId());
        writeStringCell(18, row, sheet, legislator.getVoteSmartId());
        writeStringCell(19, row, sheet, legislator.getFecId());
        writeStringCell(20, row, sheet, legislator.getGovTrackId());
        writeStringCell(21, row, sheet, legislator.getCrpId());
        writeStringCell(22, row, sheet, legislator.getEventfulId());
        writeStringCell(23, row, sheet, legislator.getCongresspediaURL());
        writeStringCell(24, row, sheet, legislator.getTwitterId());
        writeStringCell(25, row, sheet, legislator.getYouTubeURL());
    }

    /**
     * Writes a string into a cell of the spreadsheet.
     * 
     * @param col
     *            the column index.
     * @param row
     *            the row index.
     * @param sheet
     *            the spreadsheet.
     * @param text
     *            the string to be written.
     * @throws WriteException
     *             if an exception occurred writing the cell.
     */
    private void writeStringCell(int col, int row, WritableSheet sheet, String text) throws WriteException {
        Label label = new Label(col, row, text);
        sheet.addCell(label);
        updateColumnTextLength(col, text);
    }

    /**
     * Writes a boolean value into a cell of the spreadsheet.
     * 
     * @param col
     *            the column index.
     * @param row
     *            the row index.
     * @param sheet
     *            the spreadsheet.
     * @param b
     *            the value to be written.
     * @throws WriteException
     *             if an exception occurred writing the cell.
     */
    private void writeBooleanCell(int col, int row, WritableSheet sheet, boolean b) throws WriteException {
        jxl.write.Boolean bool = new Boolean(col, row, b);
        sheet.addCell(bool);
        updateColumnTextLength(col, b ? "TRUE" : "FALSE");
    }

    /**
     * Updates the length of a column using the length of a string.
     * 
     * @param col
     *            the column index.
     * @param text
     *            the text whose length must be used to recompute the column's length.
     */
    private void updateColumnTextLength(int col, String text) {
        MutableInt textLength = colTextLengths.get(col);
        if (textLength == null) {
            textLength = new MutableInt();
            colTextLengths.put(col, textLength);
        }
        textLength.setValue(Math.max(textLength.intValue(), text.length()));
    }

    /** Adjusts the size of each column. */
    private void adjustColumnsSize() {
        for (Map.Entry<Integer, MutableInt> entry : colTextLengths.entrySet()) {
            sheet.setColumnView(entry.getKey(), entry.getValue().intValue() + 2);
        }
    }

}
